// *****************************************************************************************

/* 
 * GRAMATYKA
 * 
 * Kouprey is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Kouprey is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Kouprey.  If not, see <http://www.gnu.org/licenses/>.
 */

if (typeof (String.prototype.trim) === "undefined") {
	String.prototype.trim = function() {
		return String(this).replace(/^\s+|\s+$/g, '');
	};
};

// check if a contains b ( a > b )
var contains = function(a, b) {

	var i = 0, j = 0, has = false;
	for (i = 0; i < b.length; ++i) {
		has = false;
		for (j = 0; j < a.length; j++)
			if (a[j] == b[i]) {
				has = true;
				break;
			}
		if (has == false)
			return false;
	}
	return true;
};
// check if a and b has common elements
var product = function(a, b) {
	var i = 0, j = 0;
	for (i = 0; i < b.length; ++i) {
		for (j = 0; j < a.length; j++)
			if (a[j] == b[i])
				return true;
	}
	return false;
};

var same = function(a,b) {
	a.sort();
	b.sort();
	if( a.length != b.length )
		return false;
	for( var i = 0; i < a.length ; i++)
		if( a[i] != b[i])
			return false;
	return true;
};

/* Declare a dictionary for our rules. */
var Rules = {};

/* Write the grammar. */
with (com.deadpixi.kouprey) {
	with (Rules) {
		Rules.Expression = Forward("Expression");

		Rules.Space = /^[\x20\x09\x0a\x0d]*/;

		Rules.Mesg = $(/^(ORIG|COPY)/, "mesg");
		Rules.List = $( [ Mesg, ".", $(/^(to|cc|bcc|ref)/, "listVal") ], "list");
		Rules.Txt = $(
				[ Mesg, ".", $(/^(body|sub|folder|from|id)/, "txtVal") ], "txt");
		Rules.Date = $( [ Mesg, ".", $(/^date/, "dateVal") ], "date");
		Rules.Num = $( [ Mesg, ".", $(/^DEPRECATED/, "numVal") ], "num");

		Rules.String = $(/^`.+?'/, "string");
		Rules.Number = $(/^[0-9]+/, "number");

		Rules.DateM = $( [ Date, Space, '+', Space, Number ], "datem");

		Rules.Coper = $(Or("<", ">", "=", "^", "pref", "suf"), "coper");

		Rules.Term = $(Or( [ Space, List, Space, Coper, Space, List ], [ Space,
				List, Space, Coper, Space, String ], [ Space, Txt, Space,
				Coper, Space, Txt ],
				[ Space, Txt, Space, Coper, Space, String ], [ Space, Date,
						Space, Coper, Space, DateM ], [ Space, Date, Space,
						Coper, Space, Date ], [ Space, Num, Space, Coper,
						Space, Num ],
				[ Space, Num, Space, Coper, Space, Number ], [ Space, "(",
						Space, Expression, Space, ")" ]), "term");

		Rules.Expression = $(
				Or( [ Space, Term, Space, $(Or("&", "|"), "boper"), Space,
						Expression ], [ Space, Term ]), "expression");
	}
}

/* Evaluate the input. */
var variables = {};
var orig;
var copy;

var evaluate = function(node) {
	// Function scope variables.
	var op; // Operation.
	var v1; // Temporary value.
	var v2; // Temporary value.
	var prop;
	var msg;

	switch (node.type) {

	case "mesg":
		if (node.value == "ORIG")
			return orig;
		else if (node.value == "COPY")
			return copy;
		else {
			alert(node.value + '. Only ORIG and COPY are allowed!');
			return null;
		}

	case "string":
		return {
			msg : null,
			type : node.type,
			prop : node.value.substr(1, node.value.length - 2)
		};

	case "number":
		return {
			msg : null,
			type : node.type,
			prop : Number(node.value)
		};

	case "list":
		msg = evaluate(node.children[0]);
		if (node.children[1].value == "to")
			prop = msg.recipients;
		else if (node.children[1].value == "cc")
			prop = msg.ccList;

		return {
			msg : msg,
			type : node.type,
			prop : prop
		};

	case "txt":
		msg = evaluate(node.children[0]);
		if (node.children[1].value == "sub")
			prop = msg.subject;
		else if (node.children[1].value == "body")
			prop = msg.body;
		else if (node.children[1].value == "folder")
			prop = msg.folder;
		else if (node.children[1].value == "from")
			prop = msg.author;
		else if (node.children[1].value == "id")
			prop = msg.messageId;

		return {
			msg : msg,
			type : node.type,
			prop : prop
		};

	case "date":
		msg = evaluate(node.children[0]);
		if (node.children[1].value == "date")
			prop = Number(msg.dateInSeconds);

		return {
			msg : msg,
			type : node.type,
			prop : prop
		};

	case "num":
		alert('num is depracated!');

	case "datem":
		msg = evaluate(node.children[0]).msg;
		prop = Number(msg.dateInSeconds) + Number(node.children[1].value);

		return {
			msg : msg,
			type : node.type,
			prop : prop
		};

	case "term":
	case "expression":
		if (node.value !== undefined) {
			return evaluate(node.value);
		}
		if (node.children.length === 1) {
			return evaluate(node.children[0]);
		}

		op = evaluate(node.children[1]);
		v1 = evaluate(node.children[0]);
		v2 = evaluate(node.children[2]);

		if (op == "&") {
			return v1 && v2;
		}
		if (op == "|") {
			return v1 || v2;
		}
		if (op == "<") {
			if ((v1.type == "txt" || v1.type == "string")
					&& (v2.type == "txt" || v2.type == "string")) {
				return (v1.prop.trim().length < v2.prop.trim().length);
			}

			else if (v1.type == "date"
					&& (v2.type == "date" || v2.type == "datem"))
				return (v1.prop < v2.prop);

			else if (v1.type == "list" && v2.type == "list")
				return contains(v2.prop, v1.prop);
			
			else if (v1.type == "string" && v2.type == "list")
				return contains(v2.prop, [v1.prop]);
			
			else
				alert(op + ' not allowed for operands: ' + v1.type + ', '
						+ v2.type);
		}
		if (op == ">") {
			if ((v1.type == "txt" || v1.type == "string")
					&& (v2.type == "txt" || v2.type == "string")) {
				return (v1.prop.trim().length > v2.prop.trim().length);
			}

			else if (v1.type == "date"
					&& (v2.type == "date" || v2.type == "datem"))
				return (v1.prop > v2.prop);

			else if (v1.type == "list" && v2.type == "list")
				return contains(v1.prop, v2.prop);
			
			else if (v1.type == "list" && v2.type == "string")
				return contains(v1.prop, [v2.prop]);

			else
				alert(op + ' not allowed for operands: ' + v1.type + ', '
						+ v2.type);
		}
		if (op == "=") {
			if ((v1.type == "txt" || v1.type == "string")
					&& (v2.type == "txt" || v2.type == "string")) {
				return (v1.prop.trim() == v2.prop.trim());
			}

			else if (v1.type == "date"
					&& (v2.type == "date" || v2.type == "datem"))
				return (v1.prop == v2.prop);

			else if (v1.type == "list" && v2.type == "list")
				return same(v1.prop, v2.prop);

			else
				alert(op + ' not allowed for operands: ' + v1.type + ', '
						+ v2.type);
		}
		if (op == "^") {
			if (v1.type == "list" && v2.type == "list")
				return product(v1.prop, v2.prop);

			else
				alert(op + ' not allowed for operands: ' + v1.type + ', '
						+ v2.type);
		}
		if (op == "pref") {
			if ((v1.type == "txt" || v1.type == "string")
					&& (v2.type == "txt" || v2.type == "string")) {
				return (v2.prop.search(v1.prop) == 0 && v2.prop.length >= v1.prop.length);
			} else
				alert(op + ' not allowed for operands: ' + v1.type + ', '
						+ v2.type);
		}
		if (op == "suf") {
			if ((v1.type == "txt" || v1.type == "string")
					&& (v2.type == "txt" || v2.type == "string")) {
				return (v2.prop.search(v1.prop) + v1.prop.length == v2.prop.length);
			} else
				alert(op + ' not allowed for operands: ' + v1.type + ', '
						+ v2.type);
		}

		alert("ERROR: Unknown operation: '" + op + "'");
		return null;
		break;

	case "coper":
		return node.value;
	case "boper":
		return node.value;

	default:
		alert("ERROR: Unknown node type: " + node.type);
		return null;
	}

};

/* Create the parser. */
var parser = new com.deadpixi.kouprey.Parser(Rules, "Expression", true);

/* The parse function. */
var parseExpr = function(input) {

	/* Parse the input. */
	var value = parser.parse(input);
	if (value === null || value === undefined) {
		alert(("ERROR: Parse error at offset: " + parser.highestOffset) + "\n"
				+ parser.getDebugTrace());
	}

	/* Output the AST. */
	// js_beautify(JSON.stringify(value));
	/* Spit out the result. */
	return value;
};

var evalExpr = function(or, cp, parsed) {
	orig = or;
	copy = cp;
	return evaluate(parsed);
};
